package tubesprogin4;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import mudus.GlobalSettings;

public class Client implements Runnable {
    private Socket socket;    
    private BufferedInputStream bi = null;
    private BufferedOutputStream bo = null;
    private ObjectInputStream oi = null;
    private String username = "hilmanbeyri";
    private boolean loggedin = false;
    private static final int SYNC_PERIOD = 10; // dalam detik
    
    String action;
    boolean skip;
    
    public Client() {       
    }   
    
    public boolean connect(String url, int port, String user, String pass) throws UnknownHostException, IOException, ClassNotFoundException {        
        initStream(url, port);
        String usrpwd = user + " " + pass;
        login(usrpwd); 
        if(acceptConfirmation()) {
            username = user;
            loggedin = true;
            return true;
        }        
        closeStream();
        return false;
    }

    public void closeConnection() throws IOException {
        socket.close();
    }
     
    public void run() {
        try {
            while(loggedin) {
                synchronize();
                Thread.sleep(SYNC_PERIOD * 1000);
            }
            System.out.println("logout diterima");
        } catch (InterruptedException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }        
    
    private void initStream(String url, int port) throws IOException {       
        socket = new Socket(url, port);
        bo = new BufferedOutputStream(socket.getOutputStream());        
        //oi = new ObjectInputStream(socket.getInputStream());
        bi = new BufferedInputStream(socket.getInputStream());
    }
    
    private void closeStream() throws IOException {        
        bo.close();
        bi.close();
        loggedin = false;
    }
    
    private void login(String usrpwd) throws IOException {        
        //String password = "hilmanbeyri";
        String message = usrpwd;
        sendMsg(bo, message.getBytes());        
    }
    
    private boolean acceptConfirmation() throws IOException, ClassNotFoundException {        
        byte[] b = new byte[10000];        
        int msglen = bi.read(b);
        String message = new String(b, 0, msglen);
        System.out.println(message);
        return message.equals("proceed");
    }                    
            
    private void synchronize() throws IOException {                                    
        //minta metalogfile
        sendMsg(bo, ClientMessage.createRequestMetaLogFile());
        //tunggu balesan metalogfile
        byte[] bmetalogfile = new byte[1000];        
        int bmllen = bi.read(bmetalogfile);        
        String smetalogfile = new String(bmetalogfile, 0, bmllen);        
        String[] temp = smetalogfile.split("\\|");
        long filesize = Long.parseLong(temp[1]);
        int pieceLength = Integer.parseInt(temp[2]);
        int npiece = Integer.parseInt(temp[3]);
                
        // bikin log file nya server
        ClientFileUtil.makeLogFile(username + "_SERVER.log", true);
        
        int k = 0;
               
        while(k < npiece) {
            //minta ke server piece ke- k            
            sendMsg(bo, ClientMessage.createRequestLogFilePieceMsg(k));
            System.out.println("minta metalogfile piece ke-" + k);
            //tunggu balasan piece ke k dari server
            byte[] piece = listenLogFilePiece(k);   
            if(piece != null) {
                System.out.println("menerima piece sepanjang : " + piece.length);
                ClientFileUtil.appendLogFile(username + "_SERVER.log", piece);
                k++;
            }else{
                System.out.println("nomor piece yang diterima salah");
            }
        }
        
        sendMsg(bo, ClientMessage.createEndRequestLogFilePieceMsg());
                        
        //bandingkan dengan log file punya diri sendiri
        File serverLogFile = ClientFileUtil.getLogFile(username + "_SERVER.log");
        File localLogFile = ClientLogFile.createLogFile();        
        compareFiles(serverLogFile.getPath(), localLogFile.getPath());
        File actionFile = new File(ClientSettings.CLIENT_LOG_PATH, "diff.log");                
        String[][] actions = createActionList(actionFile);
        for(int i=0;i<actions.length;i++) {            
            if(actions[i][2].equals("true") && actions[i][4].equals("download")) {                                
                download(actions[i][0], actions[i][1]);                
            }else if(actions[i][2].equals("true") && actions[i][4].equals("upload")) {
                upload(actions[i][0], actions[i][1]); 
            }else if(actions[i][2].equals("false")) {
                File dir = ClientFileUtil.getFile(actions[i][0], actions[i][1]);
                if(!dir.exists()) {
                    dir.mkdirs();
                }
            }
        }
        //simpan log hasil terakhir sinkronisasi
        ClientLogFile.createLogFile("last_synchronized.log");
    }
    
    private void download(String filepath, String filename) throws IOException {
        sendMsg(bo, ClientMessage.createRequestMetafileMsg(filepath, filename));        
        
        System.out.println("menunggu balasan metafile...");
        byte[] bmetafile = new byte[10000];        
        int msglen = bi.read(bmetafile);        
        String smetafile = new String(bmetafile, 0, msglen);        
        String[] temp = smetafile.split("\\|");
        long filesize = Long.parseLong(temp[1]);
        int pieceLength = Integer.parseInt(temp[2]);
        int npiece = Integer.parseInt(temp[3]);
        
        //simpan hasil download ke file temp
        ClientFileUtil.makeFile(filepath, filename + ".TEMP");
        
        int k = 0;        
        System.out.println("filesize : " + filesize + " piece_length : " + pieceLength + "  npiece : " + npiece);
        
        while(k < npiece) {
            //minta ke server piece ke- k            
            sendMsg(bo, ClientMessage.createRequestFilePieceMsg(filepath, filename, k));
            //tunggu balasan piece ke k dari server
            byte[] piece = listenFilePiece(k);   
            if(piece != null) {
                System.out.println("menerima piece sepanjang : " + piece.length);
                ClientFileUtil.appendFile(filepath, filename + ".TEMP", piece);
                k++;
            }else{
                System.out.println("nomor piece yang diterima salah");
            }
        }
        
        //delete file dengan nama filename jika ada, dan ubah file temp namanya menjadi file asli
        File oldFile = ClientFileUtil.getFile(filepath, filename);
        if(oldFile.exists())
            oldFile.delete();
        File newFile = ClientFileUtil.getFile(filepath, filename + ".TEMP");
        boolean b = newFile.renameTo(oldFile);        
        
        sendMsg(bo, ClientMessage.createEndRequestFilePiece());
    }
    
    private void upload(String filepath, String filename) throws FileNotFoundException, IOException {
        File file = ClientFileUtil.getFile(filepath, filename);        
        
        sendMsg(bo, ClientMessage.createUploadMetaFileMsg(file));
        //tunggu balesan persetujuan upload
        byte[] bgrant = new byte[1000];        
        int bgrantlen = bi.read(bgrant);
        String sgrant = new String(bgrant, 0, bgrantlen);
        System.out.println(sgrant);
        
        int k = 0;
        int npiece = (int) Math.ceil(file.length() * 1.0d / GlobalSettings.PIECE_LENGTH);
        
        if(sgrant.equals("granted")) {
            
            RandomAccessFile raf = new RandomAccessFile(file,"r");            
            
            while(k < npiece) { 
                // kirim piece ke-ipiece                     
                long remaining = file.length() - (k * GlobalSettings.PIECE_LENGTH);
                int piecelen = (int) ((remaining > GlobalSettings.PIECE_LENGTH) ? GlobalSettings.PIECE_LENGTH : remaining); 
                byte[] piece = new byte[piecelen];                                        
                raf.readFully(piece);          
                sendMsg(bo, ClientMessage.createUploadFilePieceMsg(k, piece)); // upload piece ke-k
                System.out.println("selesai mengirimkan piece ke-" + k);
                //tunggu ACK
                byte[] bACK = new byte[100];
                int bACKlen = bi.read(bACK);
                String sACK = new String(bACK, 0, bACKlen);
                String[] sACKs = sACK.split(" ");                
                if(sACKs[0].equals("ACK") && sACKs[1].equals("" + k)) {
                    k++;
                }else{
                    System.out.println("gagal ACK");
                }                                
            }
            
            sendMsg(bo, ClientMessage.createEndUploadFile());
        }        
    }
    
    private void requestDeleteOnServer(String filepath, String filename) throws IOException {
        
        sendMsg(bo, ClientMessage.createRequestDeleteOnServerMsg(filepath, filename));
        
    }
    
    private void deleteOnLocal(String filepath, String filename) {
        File file = new File(filepath, filename);
        if(file.exists())
            file.delete();
    }
    
    //mendengarkan piece k
    protected byte[] listenFilePiece(int k) throws IOException {
        byte[] bmsg = new byte[GlobalSettings.PIECE_LENGTH + 1000];        
        int bmsglen = bi.read(bmsg);        
        ByteBuffer bb = ByteBuffer.allocate(4);        
        bb.put(bmsg, 0, 4);                
        int ipiece = bb.getInt(0);                         
                        
        if(ipiece == k) {
            byte[] piece = new byte[bmsglen - 4];
            System.arraycopy(bmsg, 4, piece, 0, bmsglen - 4);
            return piece;
        }
        
        return null;
    }               
    
    protected byte[] listenLogFilePiece(int k) throws IOException {
        byte[] bmsg = new byte[GlobalSettings.PIECE_LENGTH + 1000];        
        int bmsglen = bi.read(bmsg);        
        ByteBuffer bb = ByteBuffer.allocate(4);        
        bb.put(bmsg, 0, 4);                
        int ipiece = bb.getInt(0);                         
                        
        if(ipiece == k) {
            byte[] piece = new byte[bmsglen - 4];
            System.arraycopy(bmsg, 4, piece, 0, bmsglen - 4);
            return piece;
        }
        
        return null;
    } 
    
    protected String[][] createActionList(File file) throws FileNotFoundException {
        Scanner scanner = new Scanner(file);
        ArrayList<String[]> temp = new ArrayList<String[]>();
        while(scanner.hasNextLine()) {
            String line = scanner.nextLine();
            String[] split = line.split("\\|");
            temp.add(split);                                    
        }
        String[][] ret= new String[temp.size()][5];
        ret = temp.toArray(ret);
        return ret;
    }
    
    private void compareFiles(String fromServer, String fromClient) throws IOException {
        String log1 = null;
        String log2 = null;
        String[] temp1;
        String[] temp2;
        FileWriter fw = new FileWriter(ClientSettings.CLIENT_LOG_PATH + "/diff.log");
        BufferedWriter out = new BufferedWriter(fw);        
        
        try {
            for (int i=0; i<2; i++) {
                if (i==0) {
                    log1 = fromServer;
                    log2 = fromClient;
                    action = "download";
                }
                else if (i==1) {
                    log1 = fromClient;
                    log2 = fromServer;
                    action = "upload";
                }
                
                FileInputStream fstream1 = new FileInputStream(log1);
                DataInputStream in1 = new DataInputStream(fstream1);
                BufferedReader br1 = new BufferedReader(new InputStreamReader(in1));
                String strLine1;           

                while ((strLine1 = br1.readLine())!=null) {
                    skip = false;
                    temp1 = strLine1.split("\\|");
                    FileInputStream fstream2 = new FileInputStream(log2);
                    DataInputStream in2 = new DataInputStream(fstream2);
                    BufferedReader br2 = new BufferedReader(new InputStreamReader(in2));
                    String strLine2;

                    while ((strLine2 = br2.readLine())!=null) {
                        temp2 = strLine2.split("\\|");
                        
                        if (temp1[0].equals(temp2[0]) && temp1[1].equals(temp2[1]) && temp1[2].equals(temp2[2])) {
                            skip = true;
                        }
                    }

                    if (!skip) {
                        out.write(strLine1 + "|" + action + "\n");
                        System.out.println(strLine1);
                    }

                    in2.close();
                }

                in1.close();                
            }
     
            compareFilesByDate(fromServer, fromClient, out);
            out.close();
        } catch (Exception e){
            System.err.println("Error: " + e.getMessage());
        }
    }
    
    public void compareFilesByDate(String fromServer, String fromClient, BufferedWriter out) throws IOException {
        String log1 = null;
        String log2 = null;
        String[] temp1;
        String[] temp2;
        
        try {
            FileInputStream fstream1 = new FileInputStream(fromServer);
            DataInputStream in1 = new DataInputStream(fstream1);
            BufferedReader br1 = new BufferedReader(new InputStreamReader(in1));
            String strLine1;           

            while ((strLine1 = br1.readLine())!=null) {
                skip = false;
                temp1 = strLine1.split("\\|");
                FileInputStream fstream2 = new FileInputStream(fromClient);
                DataInputStream in2 = new DataInputStream(fstream2);
                BufferedReader br2 = new BufferedReader(new InputStreamReader(in2));
                String strLine2;

                while ((strLine2 = br2.readLine())!=null) {
                    //skip = false;
                    temp2 = strLine2.split("\\|");

                    if (temp1[0].equals(temp2[0]) && temp1[1].equals(temp2[1]) && temp1[2].equals(temp2[2])) {
                        long l1 = Long.parseLong(temp1[3]);
                        long l2 = Long.parseLong(temp2[3]);

                        if (l1>l2) {
                            action = "download";
                            out.write(strLine1 + "|" + action + "\n");
                            System.out.println(strLine1);
                        }
                        else if (l1<l2) {
                            action = "upload";
                            out.write(strLine2 + "|" + action + "\n");
                            System.out.println(strLine2);
                        }
                    }

                }

                in2.close();
            }

            in1.close();                
            out.close();
        } catch (Exception e){
            System.err.println("Error: " + e.getMessage());
        }
    }
    
    public void changeRepoDir(String dest) {
        String from = ClientSettings.CLIENT_REPO_PATH;
        File file = new File(from);
        File dir = new File(dest);
        
        for(File child : file.listFiles()) {
            if(file.getName().equals(dir.getName())) {
                continue;
            }
            File newChild = new File(dir, child.getName());
            child.renameTo(newChild);
        }
                
        //boolean success = file.renameTo(new File(dir, file.getName()));                
        ClientSettings.CLIENT_REPO_PATH = dest;        
    }
    
    private void moveDir(String from, String to) {
        
    }
    
    private void sendMsg(BufferedOutputStream out, byte[] msg) throws IOException {
        //System.out.println("mengirimkan " + new String(msg));
        out.write(msg);
        out.flush();
    }
    
    public static void main(String[] args) throws IOException, UnknownHostException, ClassNotFoundException {
        Client client = new Client();
    }   
}
